using System;
using System.Collections.Generic;
using System.Diagnostics;
using Smith.AMC.Operations;
using Smith.AMC.PerformanceCounters;
using Wintellect.Threading.AsyncProgModel;

namespace Smith.AMC.Network
{
    /// <summary>
    /// Implementation of the <see cref="ISocketWorker"/>
    /// </summary>
    public class SocketWorker : ISocketWorker
    {
        /// <summary>
        /// The server pool in use for socket worker
        /// </summary>
        private readonly ServerPool serverPool;

        /// <summary>
        /// The timeout for aquiring a client socket
        /// </summary>
        private readonly TimeSpan timeout;

        /// <summary>
        /// The performance counter controller
        /// </summary>
        private readonly PerformanceCounterController perfController;

        /// <summary>
        /// The stopwatch used to populate performance counters
        /// </summary>
        [ThreadStatic]
        private static Stopwatch performanceCounterStopWatch;

        /// <summary>
        /// Initializes a new instance of the <see cref="MemcachedClient"/> class.
        /// </summary>
        /// <param name="serverPool">The socket pool to use for connections.</param>
        /// <param name="timeout">The timeout setting for all operations against the memcached server.</param>
        public SocketWorker(ServerPool serverPool, TimeSpan timeout)
        {
            this.serverPool = serverPool;
            this.timeout = timeout;
            perfController = new PerformanceCounterController(Process.GetCurrentProcess().ProcessName);
        }


        /// <summary>
        /// Begins an asynchronous operation against the memcached server.
        /// </summary>
        /// <typeparam name="T">Type of item to return from the socket operation</typeparam>
        /// <param name="operation">The operation to execute against the memcached server.</param>
        /// <param name="callback">The <see cref="System.AsyncCallback"/> delegate that is executed when the BeginSocketOperation operation completes</param>
        /// <param name="state">An object that contains any additional user-defined data.</param>
        /// <returns>
        /// An <see cref="System.IAsyncResult"/> that represents the asynchronous call.
        /// </returns>
        public IAsyncResult BeginSocketOperation<T>(Operation operation, AsyncCallback callback, object state)
        {
            AsyncEnumerator<OperationResponse<T>> ae = new AsyncEnumerator<OperationResponse<T>>();
            return ae.BeginExecute(DoSocketOperation(ae, operation), callback, state);
        }

        /// <summary>
        /// Handles the end of the asynchronous socket operation
        /// </summary>
        /// <typeparam name="T">Type of item to return from the socket operation</typeparam>
        /// <param name="asyncResult">An <see cref="System.IAsyncResult"/> that represents the asynchronous call.</param>
        /// <returns>
        /// An operation response with the response of the memcached server.
        /// </returns>
        public OperationResponse<T> EndSocketOperation<T>(IAsyncResult asyncResult)
        {
            return AsyncEnumerator<OperationResponse<T>>.FromAsyncResult(asyncResult).EndExecute(asyncResult);
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            perfController.Dispose();
        }

        /// <summary>
        /// Main worker method. This method is used by all the BeginXX methods
        /// </summary>
        /// <typeparam name="T">The type of data to work on</typeparam>
        /// <param name="ae">The async enumerator.</param>
        /// <param name="operation">The operation.</param>
        /// <returns>An IEnumerator that the async enumerator iterates over</returns>
        private IEnumerator<int> DoSocketOperation<T>(AsyncEnumerator<OperationResponse<T>> ae, Operation operation)
        {
            if (performanceCounterStopWatch == null)
            {
                performanceCounterStopWatch = Stopwatch.StartNew();
            }
            else
            {
                performanceCounterStopWatch.Start();
            }

            IClientSocket clientSocket = serverPool.AquireSocket(timeout, operation.Key);
            performanceCounterStopWatch.Stop();

            // Icrement the average time
            perfController.IncrementAverageTime(performanceCounterStopWatch.ElapsedTicks);
            performanceCounterStopWatch.Reset();

            clientSocket.BeginWrite<T>(operation, ae.End(), null);
            yield return 1;

            try
            {
                OperationResponse<T> operationResponse = clientSocket.EndWrite<T>(ae.DequeueAsyncResult());
                ae.Result = operationResponse;
            }
            catch
            {
                perfController.ErrorsPerSecond.Increment();
                clientSocket.Dead = true;

                // We don't want to prevent a client from getting an exception
                // Potentially just fail silently with a log, so client will go to whatever persistent store that this client
                // is caching for.
                throw;
            }
            finally
            {
                serverPool.ReleaseSocket(clientSocket);
                IncrementOperationPerformanceCounter(operation.Name);
            }
        }

        /// <summary>
        /// Increments the operation performance counter for the given operation name
        /// </summary>
        /// <param name="operationName">Name of the operation.</param>
        private void IncrementOperationPerformanceCounter(string operationName)
        {
            perfController.TotalCacheOperationsPerSecond.Increment();

            switch (operationName)
            {
                case OperationNames.Add:
                    perfController.AddsPerSecond.Increment();
                    break;
                case OperationNames.Append:
                    perfController.AppendsPerSecond.Increment();
                    break;
                case OperationNames.CheckAndSet:
                    perfController.CASPerSecond.Increment();
                    break;
                case OperationNames.Decrement:
                    perfController.DecrementsPerSecond.Increment();
                    break;
                case OperationNames.Delete:
                    perfController.DeletesPerSecond.Increment();
                    break;
                case OperationNames.Get:
                case OperationNames.Gets:
                    perfController.GetsPerSecond.Increment();
                    break;
                case OperationNames.Increment:
                    perfController.IncrementsPerSecond.Increment();
                    break;
                case OperationNames.Prepend:
                    perfController.PrependsPerSecond.Increment();
                    break;
                case OperationNames.Replace:
                    perfController.ReplacesPerSecond.Increment();
                    break;
                case OperationNames.Set:
                    perfController.SetsPerSecond.Increment();
                    break;
                default:
                    throw new InvalidOperationException(string.Format("Unknown operation:{0} - no performance counter available", operationName));
            }
        }
    }
}